package cn.blu10ph.trustshare.service.impl;

import cn.blu10ph.trustshare.bean.msg.BaseMsg;
import cn.blu10ph.trustshare.bean.msg.TrustMsg;
import cn.blu10ph.trustshare.bean.node.TrustNode;
import cn.blu10ph.trustshare.bean.notice.AnnounceNoticeMsg;
import cn.blu10ph.trustshare.bean.notice.TrustNotice;
import cn.blu10ph.trustshare.constant.Constant;
import cn.blu10ph.trustshare.service.DHTMsgService;
import cn.blu10ph.trustshare.util.ConvertUtil;
import cn.blu10ph.trustshare.util.HashAndSignUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramPacket;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * <p> DHTAnnounceNoticeMsgServiceImpl </p >
 *
 * @author cxx
 * @date 2024/4/6 22:22
 */
@Slf4j
@Component
public class DHTAnnounceNoticeMsgServiceImpl extends DHTMsgBaseService<TrustNotice> implements DHTMsgService<TrustNotice> {

    @Override
    public void sendQuery(String nodeId, String hostname, int port, TrustNotice trustNotice) throws Exception {
        if(ObjectUtils.isEmpty(trustNotice)){
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] params is empty";
            log.error(errMsg);
            throw new Exception(errMsg);
        }
        if(!StringUtils.hasText(trustNotice.getBaseHash())){
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] baseHash is empty";
            log.error(errMsg);
            throw new Exception(errMsg);
        }
        if(!routingTable.getNodeId().equals(trustNotice.getFromNode())){
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] fromNode is not myself";
            log.error(errMsg);
            throw new Exception(errMsg);
        }

        // notice sign check
        if(!HashAndSignUtil.checkNoticeHash(trustNotice)){
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] check notice hash failed";
            log.error(errMsg);
            throw new Exception(errMsg);
        }
        PGPPublicKey publicKey = pgpManager.getPublicKey(trustNotice.getFromNode());
        // notice infoHash check
        if(!HashAndSignUtil.checkNoticeSign(trustNotice, publicKey)){
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] check notice sign failed";
            log.error(errMsg);
            throw new Exception(errMsg);
        }

        TrustNotice baseNotice = noticeService.get(trustNotice.getBaseHash());
        if(ObjectUtils.isEmpty(baseNotice)){
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] " +
                    "base msg[" + baseNotice.getInfoHash() + "] is not found";
            log.error(errMsg);
            throw new Exception(errMsg);
        }
        if(!nodeId.equals(baseNotice.getFromNode())){
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] " +
                    "base msg[" + baseNotice.getInfoHash() + "] is not belong to target";
            log.error(errMsg);
            throw new Exception(errMsg);
        }

        TrustNotice rootNotice;
        if (StringUtils.hasText(baseNotice.getBaseHash())) {
            rootNotice = noticeService.get(baseNotice.getBaseHash());
            if (StringUtils.hasText(rootNotice.getBaseHash())) {
                String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] level error";
                log.error(errMsg);
                throw new Exception(errMsg);
            }
        } else {
            rootNotice = baseNotice;
        }

        // send to base msg node
        super.sendQuery(nodeId, hostname, port, trustNotice);

        // send to root msg node too
        if(!nodeId.equals(rootNotice.getFromNode())) {
            TrustNode rootMsgNode = routingTable.getNodeById(rootNotice.getFromNode());
            super.sendQuery(rootMsgNode, trustNotice);
        }
    }

    @Override
    public void sendReturn(ChannelHandlerContext ctx, DatagramPacket packet, TrustNode node, TrustMsg msg) {
        TrustNotice trustNotice;
        try {
            if (StringUtils.hasText(msg.getParams())) {
                trustNotice = ConvertUtil.str2Obj(msg.getParams(), TrustNotice.class);
            } else {
                trustNotice = new TrustNotice();
            }
        } catch (JsonProcessingException ex) {
            ex.printStackTrace();
            return;
        }

        if (!node.getNodeId().equals(trustNotice.getFromNode())) {
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] baseHash is not send by from";
            log.error(errMsg);
            return;
        } else if (!StringUtils.hasText(trustNotice.getBaseHash())) {
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] baseHash is empty";
            log.error(errMsg);
            return;
        }

        // notice sign check
        if(!HashAndSignUtil.checkNoticeHash(trustNotice)){
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] check notice hash failed";
            log.error(errMsg);
            return;
        }
        PGPPublicKey publicKey = pgpManager.getPublicKey(trustNotice.getFromNode());
        // notice infoHash check
        if(!HashAndSignUtil.checkNoticeSign(trustNotice, publicKey)){
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] check notice sign failed";
            log.error(errMsg);
            return;
        }

        TrustNotice baseNotice = noticeService.get(trustNotice.getBaseHash());
        if(ObjectUtils.isEmpty(baseNotice)){
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] " +
                    "base msg[" + trustNotice.getBaseHash() + "] is not found";
            log.error(errMsg);
            return;
        }
        TrustNotice rootNotice;
        if (StringUtils.hasText(baseNotice.getBaseHash())) {
            rootNotice = noticeService.get(baseNotice.getBaseHash());
            if (StringUtils.hasText(rootNotice.getBaseHash())) {
                String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] level error";
                log.error(errMsg);
                return;
            }
        } else {
            rootNotice = baseNotice;
        }
        if (!(routingTable.getNodeId().equals(baseNotice.getFromNode())
                || routingTable.getNodeId().equals(rootNotice.getFromNode())
        )) {
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] is not belong to myself";
            log.error(errMsg);
            return;
        }

        // storage notice
        noticeService.add(trustNotice);

        // return receive
        BaseMsg msgObj = getBaseMsg(Constant.MSG_DIRECTION_RETURN, node.getNodeId());
        AnnounceNoticeMsg announceNoticeMsg = new AnnounceNoticeMsg(trustNotice.getInfoHash());
        try {
            msgObj.setParams(ConvertUtil.obj2Str(announceNoticeMsg));
            response(node, ctx, packet.sender(), msgObj);
        } catch (JsonProcessingException ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public void receive(ChannelHandlerContext ctx, DatagramPacket packet, TrustNode node, TrustMsg msg) {
        AnnounceNoticeMsg announceNoticeMsg;
        try {
            announceNoticeMsg = ConvertUtil.str2Obj(msg.getParams(), AnnounceNoticeMsg.class);
        } catch (JsonProcessingException ex) {
            ex.printStackTrace();
            return;
        }

        TrustNotice trustNotice = noticeService.get(announceNoticeMsg.getInfoHash());
        if(ObjectUtils.isEmpty(trustNotice)){
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] is not found";
            log.error(errMsg);
            return;
        }
        if(!routingTable.getNodeId().equals(trustNotice.getFromNode())){
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] is not myself send";
            log.error(errMsg);
            return;
        }

        TrustNotice baseNotice = noticeService.get(trustNotice.getInfoHash());
        if(ObjectUtils.isEmpty(baseNotice)){
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] " +
                    "base msg[" + trustNotice.getBaseHash() + "] is not found";
            log.error(errMsg);
            return;
        }
        TrustNotice rootNotice;
        if (StringUtils.hasText(baseNotice.getBaseHash())) {
            rootNotice = noticeService.get(baseNotice.getBaseHash());
            if (StringUtils.hasText(rootNotice.getBaseHash())) {
                String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] level error";
                log.error(errMsg);
                return;
            }
        } else {
            rootNotice = baseNotice;
        }
        if (node.getNodeId().equals(baseNotice.getFromNode())) {
            trustNotice.setBaseReceive(Boolean.TRUE);
        } else if (node.getNodeId().equals(rootNotice.getFromNode())) {
            trustNotice.setRootReceive(Boolean.TRUE);
        } else {
            String errMsg = getType() + " msg[" + trustNotice.getInfoHash() + "] " +
                    "receive node[" + node.getNodeId() + "] is not base or root";
            log.error(errMsg);
        }
    }

    @Override
    protected Class<TrustNotice> getParamsType() {
        return TrustNotice.class;
    }

    @Override
    public String getType() {
        return Constant.MSG_TYPE_ANNOUNCE_NOTICE;
    }

}
